(* Cr�me CAraMeL Compiler (Triple C) - Michele Caceffo - 2008 *)

open Exceptions;;
open Syntaxtree;;
open Intermediate;;
open Declaration;;
open Expressions;;
open Print;;

(** In this module there is the function to create the Intermediate Code for commands *)
let rec getLastArrayPointer (stackoff:element) (idx:int) (acc:int) (ic:intermediateCode) = 
        if (acc=idx) then stackoff 
                     else (let reg=ic#getReg in
                           ic#addInstr (GAP,(Reg(reg,DInt)), stackoff,Null);
                           getLastArrayPointer (Reg(reg,DInt)) idx (acc+1) ic
                          )
   


(** Creates the instructions for a command and adds the instructions to the Intermediate Code list of instructions 
    @param c The command to analyze
    @param dect The declaration table
    @param proct The procedure table
    @param ic The intermediateCode class instance
    @return Nothing, but the intermediate Code class is updated with all the instructions created
*)
let rec genCommand (c: cmd) (dect: dec_table) (proct: proc_table) (ic: intermediateCode): unit = match c with

      VecDelete(i,x)              ->(*let (idx,dim,Off(off,DInt,b))=getVecDeclaration i 0 (-1) dect in
                                     if (x>dim) then raise (DIM_ARRAY_ERROR ("index bigger than array dimension /n"))
                                                else (if (x=1) then (
                                                      let first_element = getLastArrayPointer (Off(off,DInt,b)) 2 0 ic in
                                                      Hashtbl.replace dect i  (idx,(dim-1),first_element); (*ERROR:first_element e' il registro contenente la posizione nello stack del secondo elemento...devo ruscire a ricreare l'offset in qualche modo *)
                                                      )else(
                                                     let prec_element = getLastArrayPointer (Off(off,DInt,b)) (x-1) 0 ic and post_element = getLastArrayPointer (Off(off,DInt,b)) (x+1) 0 ic in
                                                     ic#addInstr (SAP,prec_element ,post_element , Null);
                                                     Hashtbl.replace dect i  (idx,(dim-1),Off(off,DInt,b));
                                                     ) )
                                    
                                     funzionamento: il ciclo while mi permette di arrivare all'elemento predecessore di quello da eliminare,poi faccio un controllo se l'elemento da eliminare
                                     e' in ultima posizione oppure all'interno
                                    *)
                                     (let (idx,dim,Off(off,DInt,b))=getVecDeclaration i 0 (-1) dect in
                                                             let reg,reg2,reg3,result, new_off=ic#getReg,ic#getReg,ic#getReg,ic#getReg,ic#stack and  rop2 = genArithmetical x dect proct ic in 
                                                             let startwhile,endwhile = ic#getLabel,ic#getLabel in
                                                               (
                                                                  ic#addInstr (CPY, Val(I(1)), Null, (Reg(reg,DInt)));
                                                                  ic#addInstr (CPY, Off(off,DInt,b), Null, (Reg(reg2,DInt)));(*assegno prevantivamente l'offset al resgistro reg2 nel caso devo sostituire il primo elemento*)
                                                                  ic#addLabel startwhile (ic#getIc);
                                                                  ic#addInstr (CNE ,Reg(reg,DInt), rop2, Reg(result,DInt));
                                                                  ic#addInstr (JNE, Reg(result,DInt), Val(I(1)), endwhile);
                                                                  ic#addInstr (ADD,(Reg(reg,DInt)) , Val(I(1)), (Reg(reg,DInt)));
                                                                  ic#addInstr (CPY,(Reg(reg2,DInt)) , Null, (Reg(reg3,DInt)));
                                                                  ic#addInstr (GAP,(Reg(reg2,DInt)),(Reg(reg3,DInt)) ,Null);
                                                                  ic#addInstr (GOTO, startwhile, Null, Null);
                                                                  ic#addLabel endwhile (ic#getIc);
                                                            
                                                                  let ctrue, endif = ic#getLabel,ic#getLabel in( 
                                                                     let reg4,reg5 = ic#getReg,ic#getReg  in
                                                                     ic#addInstr (GAP,(Reg(reg4,DInt)),(Reg(reg2,DInt)) ,Null); (*reg4 contiene l'offset dell'elemento da eliminare*)
                                                                     ic#addInstr (GAP,(Reg(reg5,DInt)),(Reg(reg4,DInt)) ,Null);(*prelevo il puntatore all'elemento successivo rispetto all'elemento da eliminare*)
                                                                     (*if per determinare se l'inserimento va fatto in mezzo all'array oppure alla fine*)
                                                                      ic#addInstr (JNE, Val(I(-1)), (Reg(reg5,DInt)), ctrue); (* result != 0  --> condition is true, so execute "then" branch *)
                                                                     (* if we arrive here, condition is false: so, execute "else" branch *)
                                                                     ic#addInstr (SAP,(Reg(reg2,DInt)),Val(I(-1)) ,Null); (*setto il puntatore al nuovo elemento*)
                                                                     ic#addInstr (GOTO, endif, Null, Null);
                                                                     ic#addLabel ctrue (ic#getIc);
                                                                     ic#addInstr (SAP,(Reg(reg2,DInt)),(Reg(reg5,DInt)) ,Null); (*setto il puntatore al nuovo elemento*)
                                                                     ic#addLabel endif (ic#getIc);
                                                                   )

                                                                 )
                                                              )

                                    
      (** Assuming that the types between Left and Right of assignment are correct, first is generated the code for the right part, 
          then the code for the left part. *)
      | Ass(l,a)                  ->  (
                                     let rop = genArithmetical a dect proct ic in
                                     match l with
                                        LVar(i)     ->  let (_, _, stackoff) = getVarDeclaration i dect in
                                                        ic#addInstr (CPY, rop, Null, stackoff) (** If the left part is a variable, only copy the value *)
                                      (*PROBLEMA: come faccio a controllare se il valore passato al vettore e' piu grande della sua grandezza massima ? in quanto la mia grandezza massima
                                                  e' all'interno di un registro*)
                                      | LVecAdd(i,x)   -> (* (let (idx,dim,Off(off,DInt,b))=getVecDeclaration i 0 (-1) dect in
                                                            if (x<=dim) then (
                                                                             let new_off= ic#stack in
                                                                             let last_element,prec_element = getLastArrayPointer (Off(off,DInt,b)) x 1 ic,getLastArrayPointer (Off(off,DInt,b)) (x-1) 1 ic in
                                                                             ic#addInstr (SAP,prec_element ,(Off(new_off, DInt, b)) , Null);
                                                                             ic#addInstr (SAP,(Off(new_off, DInt, b)) ,last_element , Null);
                                                                             ic#addInstr (SAV,(Off(new_off, DInt, b)) ,rop , Null);
                                                                             Hashtbl.replace dect i  (idx,(dim-1),Off(off,DInt,b));
                                                                        ) 
                                                                        else (
                                                                             let new_off= ic#stack in
                                                                             let last_element = getLastArrayPointer (Off(off,DInt,b)) dim 1 ic in
                                                                             ic#addInstr (SAP,last_element ,(Off(new_off, DInt, b)) , Null);
                                                                             ic#addInstr (SAV,(Off(new_off, DInt, b)) ,rop , Null);
                                                                        ) il contatore parte da 1 di modo da arrivare all'elemento precedente rispetto alla posizione in cui voglio inserire l'elemento     *)

                                                             (let (idx,dim,Off(off,DInt,b))=getVecDeclaration i 0 (-1) dect in
                                                             let reg,reg2,reg3,result, new_off=ic#getReg,ic#getReg,ic#getReg,ic#getReg,ic#stack and  rop2 = genArithmetical x dect proct ic in 
                                                             let startwhile,endwhile = ic#getLabel,ic#getLabel in
                                                               (
                                                                  ic#addInstr (CPY, Val(I(1)), Null, (Reg(reg,DInt)));
                                                                  ic#addInstr (CPY, Off(off,DInt,b), Null, (Reg(reg2,DInt)));(*assegno prevantivamente l'offset al resgistro reg2 nel caso devo sostituire il primo elemento*)
                                                                  ic#addLabel startwhile (ic#getIc);
                                                                  ic#addInstr (CNE ,Reg(reg,DInt), rop2, Reg(result,DInt));
                                                                  ic#addInstr (JNE, Reg(result,DInt), Val(I(1)), endwhile);
                                                                  ic#addInstr (ADD,(Reg(reg,DInt)) , Val(I(1)), (Reg(reg,DInt)));
                                                                  ic#addInstr (CPY,(Reg(reg2,DInt)) , Null, (Reg(reg3,DInt)));
                                                                  ic#addInstr (GAP,(Reg(reg2,DInt)),(Reg(reg3,DInt)) ,Null);
                                                                  ic#addInstr (GOTO, startwhile, Null, Null);
                                                                  ic#addLabel endwhile (ic#getIc);
                                                            
                                                                  let ctrue, endif = ic#getLabel,ic#getLabel in( 
                                                                     let reg4 = ic#getReg  in
                                                                     ic#addInstr (GAP,(Reg(reg4,DInt)),(Reg(reg2,DInt)) ,Null);
                                                                     (*if per determinare se l'inserimento va fatto in mezzo all'array oppure alla fine*)
                                                                      ic#addInstr (JNE, Val(I(-1)), (Reg(reg4,DInt)), ctrue); (* result != 0  --> condition is true, so execute "then" branch *)
                                                                     (* if we arrive here, condition is false: so, execute "else" branch *)
                                                                     ic#addInstr (SAP,(Reg(reg2,DInt)),(Off(new_off, DInt, b)) ,Null); (*setto il puntatore al nuovo elemento*)
                                                                     ic#addInstr (SAP,(Off(new_off, DInt, b)), Val(I(-1)) ,Null);(*setto il puntatore del nuovo elemento all'elemento successivo nell'array*)
                                                                     ic#addInstr (GOTO, endif, Null, Null);
                                                                     ic#addLabel ctrue (ic#getIc);
                                                                     ic#addInstr (SAP,(Reg(reg2,DInt)),(Off(new_off, DInt, b)) ,Null); (*setto il puntatore al nuovo elemento*)
                                                                     ic#addInstr (SAP,(Off(new_off, DInt, b)), (Reg(reg4,DInt)) ,Null);(*setto il puntatore del nuovo elemento all'elemento successivo nell'array*)
                                                                     ic#addLabel endif (ic#getIc);
                                                                     ic#addInstr (SAV, (Off(new_off, DInt, b)) ,rop , Null);
                                                                   )

                                                                 )
                                                              )
                                      | LVecModify(i,x)-> (*(let (idx,dim,Off(off,DInt,b))=getVecDeclaration i 0 (-1) dect in
                                                            if (x<=dim) then (
                                                                             let last_element = getLastArrayPointer (Off(off,DInt,b)) x 0 ic in
                                                                             ic#addInstr (SAV,last_element ,rop , Null);
                                                                        )
                                                                        else raise (DIM_ARRAY_ERROR ("index bigger than array dimension /n")) 
                                                            )*)
                                                            (let (idx,dim,Off(off,DInt,b))=getVecDeclaration i 0 (-1) dect in
                                                             let reg,reg2,reg3,result=ic#getReg,ic#getReg,ic#getReg,ic#getReg and  rop2 = genArithmetical x dect proct ic in 
                                                             let startwhile,endwhile = ic#getLabel,ic#getLabel in
                                                               (
                                                                ic#addInstr (CPY, Val(I(0)), Null, (Reg(reg,DInt)));
                                                               ic#addInstr (CPY, Off(off,DInt,b), Null, (Reg(reg2,DInt)));
                                                               
                                                               ic#addLabel startwhile (ic#getIc);
                                                               ic#addInstr (CNE ,Reg(reg,DInt), rop2, Reg(result,DInt));
                                                               ic#addInstr (JNE, Reg(result,DInt), Val(I(1)), endwhile);
                                                               ic#addInstr (ADD,(Reg(reg,DInt)) , Val(I(1)), (Reg(reg,DInt)));
                                                               ic#addInstr (CPY,(Reg(reg2,DInt)) , Null, (Reg(reg3,DInt)));
                                                               ic#addInstr (GAP,(Reg(reg2,DInt)),(Reg(reg3,DInt)) ,Null);
                                                               ic#addInstr (GOTO, startwhile, Null, Null);
                                                               ic#addLabel endwhile (ic#getIc);
								ic#addInstr (SAV,(Reg(reg2,DInt)) ,rop , Null);
                                                              )


                                                            )
                                      | LMat(i,x,y) ->  let xRes = genArithmetical x dect proct ic and
                                                        yRes = genArithmetical y dect proct ic and
                                                        r1,r2,r3 = (ic#getReg),(ic#getReg),(ic#getReg) and
                                                        (_, dim, stackoff) = getVecDeclaration i 0 0 dect in (
                                                            ic#addInstr (MUL, (Val(I(dim))), xRes, (Reg(r1,DInt)));
                                                            ic#addInstr (ADD, (Reg(r1,DInt)), yRes, (Reg(r2,DInt)));
                                                            ic#addInstr (ASET, stackoff, (Reg(r2,DInt)), rop); 
                                                            (** Position into the memory of a matrix is (dim_of_line * number_of_lines) + number_of_columns 
                                                                this value is used into the ASET instruction
                                                            *)
                                                        )
                                    )
      (** A block is a list of commands, so when there is something into the list the genCommand is invoked on the element and then on 
          the remaining list; when the list is empty is added a No-Operation Instruction          
      *)
    | Blk(cmdl)                 ->  (
                                     match cmdl with
                                         []      ->  let newinstr = (NOP, Null, Null, Null) in
                                                     ic#addInstr newinstr
                                       | x::xs   ->  genCommand x dect proct ic; genCommand (Blk(xs)) dect proct ic
                                    )
      (** The write operation is converted into an OUT instruction *)
    | Write(ex)                 ->  (
                                     let newinstr = (OUT, (genArithmetical ex dect proct ic), Null, Null) in
                                     ic#addInstr newinstr
                                    )
      (** First are generated the labels for the start and the end of the while: the start is setted at the current point,
          then is generated the boolean condition and the register where the result of the compare is saved. After this the 
          command of the while is generated, the GOTO instruction is added and finally is setted the end Label 
      *)
    | While(b,c)                ->  (
                                     let startwhile,endwhile = ic#getLabel,ic#getLabel in
                                        (
                                         ic#addLabel startwhile (ic#getIc);
                                         let result = (genBoolean b dect proct ic) in
                                         ic#addInstr (JNE, result, Val(I(1)), endwhile);
                                         (genCommand c dect proct ic);
                                         ic#addInstr (GOTO, startwhile, Null, Null);
                                         ic#addLabel endwhile (ic#getIc);
                                        )
                                    )
    (** Here are generated 2 labels, one for the else branch and one for the end of the if; the code for the boolean compare 
        is generated, then the code of c1 (if the condition is true) with a GOTO to the end of the if command and finally 
        the code of c2 (if the condition is false)
    *)
    | Ite(b,c1,c2)              ->  (
                                     let ctrue, endif = ic#getLabel,ic#getLabel in 
                                        (
                                         let result = (genBoolean b dect proct ic) in
                                         ic#addInstr (JNE, result, Val(I(0)), ctrue); (* result != 0  --> condition is true, so execute "then" branch *)
                                         (genCommand c2 dect proct ic); (* if we arrive here, condition is false: so, execute "else" branch *)
                                         ic#addInstr (GOTO, endif, Null, Null);
                                         ic#addLabel ctrue (ic#getIc);
                                         (genCommand c1 dect proct ic);
                                         ic#addLabel endif (ic#getIc);
                                        )
                                    )
    (** Here are generated the code for the list of parameters and then the code for the function call *)
    | PCall(Ide(id),plist)      ->  (
                                     (genParam plist dect proct ic);
                                     ic#addInstr (CALL, Subr(id), Null, Null);
                                    )
